package it.ap.sa.core.audit.config;

import java.util.List;

import org.audit4j.core.dto.Field;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import it.ap.sa.core.configuration.iservice.IService;
import it.ap.sa.core.configuration.service.ConfigurationService;
import it.ap.sa.core.confservice.event.CoreServiceEventLoad;
import it.ap.sa.core.confservice.event.CoreServiceEventReload;
import it.ap.sa.core.utils.costants.ConfigurationConstants;
import it.ap.sa.core.utils.costants.ReloadServiceConstants;


/** 
 * TODO  Si può fare con Spring da valutare !!!
 * http://javarticles.com/2015/05/spring-lookup-method-example.html
 * http://stackoverflow.com/questions/12800769/replace-spring-bean-in-one-context-with-mock-version-from-another-context
 */

/**
 * Servizio di Audit che implementa i metodi per il suo utilizzo:
 *   - determinazione del writer;
 *   - scrittura evento;
 *   - listener per l'evento di "load" e "reload".
 * 
 * @author marianna
 */
@Service
public class AuditService implements IService {
	
	public static final Logger logger = LoggerFactory.getLogger(AuditService.class.getName());
	
	/** Audit writer di default -->> Audit4j - Scrittura su DB **/
	private static final String DEFAULT_AUDIT_WRITER = Audit4JWriter.class.getName();
	 
	
	private AuditService(){}

	private static IAuditWriter currentWriter;
	

	/**
	 * Ritorna il writer attivo.
	 * 
	 * @return {@link IAuditWriter}
	 */
	public static IAuditWriter getInstance() {
		synchronized (IAuditWriter.class) {
			if (currentWriter == null) {
				logger.error(">>>>>>>>>>>>>>>> Nessun writer Audit definito <<<<<<<<<<<<<<<");
				
				throw new RuntimeException("Audit non inizializzato (writer null)");
			}
		}
		return currentWriter;
	}

	/**
	 * Funzione che determina il writer da usare in base al parametro di configurazione AUDIT_WRITER_CLASS
	 * presente nell'oggetto {@link IConfiguration}.
	 * 
	 * @return {@link IAuditWriter}
	 */
	private static IAuditWriter buildWriter(){
		try {
			logger.info(">>>>>>>>>>>>>>>>>>>>>>> Building writer ... IN CORSO <<<<<<<<<<<<<<<<< ");
			
			IAuditWriter writerActive = null;
			
			try{
				/**
				 * Per default il writer attivo è quello di default.
				 */
				Class<?> clazz = Class.forName(DEFAULT_AUDIT_WRITER);
		    	if(clazz != null){
		    		writerActive = (IAuditWriter) clazz.newInstance();
		    	}
			}catch(ClassNotFoundException e){
				logger.error("La classe definita come writer di default non è stata trovata .. continuo nell'esecuzione " + e, e);
			}
			
			/**
			 * Recupero dall'oggetto {@link IConfiguration} la classe writer impostata
			 */
			String writerClass = ConfigurationService.getConfig().getString(ConfigurationConstants.AUDIT_WRITER_CLASS.name());
			
			if(StringUtils.hasText(writerClass)){
				logger.info("Classe audit writer definita da configurazione: " + writerClass + " ... Controllo che esiste ...");
				
				try{
					// get class che implementa IAuditWriter 
					Class<?> clazz = Class.forName(writerClass);
					
					if(clazz != null){
						logger.info("Trovato auditer... " + clazz);
			    		writerActive = (IAuditWriter) clazz.newInstance();
			    	}else
						logger.warn("Classe auditer non trovata .. uso quella di default ");

				}catch(BeansException e){
					logger.error("La classe definita come writer da configurazione non è stata trovata .. uso quella di default " + e, e);
				}
			}else
			   logger.error("Nessun writer trovato nelle configurazioni... ");
			
			logger.info(">>>>>>>>>>>>>>>>>>>>>>> Building writer ... TERMINATO <<<<<<<<<<<<<<<<< ");
			
			return writerActive;
		}catch(Exception e){
			logger.error("Error build writer ... continue anyway " + e, e);
			return null;
		}
	}
	
	/**
	 * Metodo per inserire in audit una revisione.
	 * 
	 * @param {@link String} action, operazione eseguita
	 * @param {@link List<Field>} fields, lista parametri
	 */
	public static void writeEvent(String action, List<Field> fields){
		try{
			if(getInstance() != null)
			   getInstance().writeEvent(action, fields);
			else
			   logger.warn("Audit non inizializzato writer null " + action);
				
		}catch(Exception e){
			logger.error("writeEvent " + e, e);
		}
	}
	
	/**
	 * Listener registrato per l'evento di "load", quindi d'inizializzazione del servizio.
	 * 
	 * @author marianna
	 */
	@EventListener
	public void onServiceEventLoad(CoreServiceEventLoad event) {
		try{
			logger.info(">>>>>>>>>>>>>>>>>>>>>>> Starting audit service ... IN CORSO <<<<<<<<<<<<<<<<< ");

			/** Determino il writer da usare **/
			currentWriter = buildWriter();
			
			/** Metodo per l'avvio programmatico di audit. **/
			currentWriter.initialize();

			logger.info(">>>>>>>>>>>>>>>>>>>>>>> Audit service started ... TERMINATO <<<<<<<<<<<<<<<<< ");
		}catch(Exception e){
			logger.error("Error load audit service ... continue anyway " + e, e);
		}
	}
	
	/**
	 * Listener registrato per l'evento di "reload", quindi il servizio deve registrare che la configurazione 
	 * è cambiata e reinizializzarsi.
	 * 
	 * @author marianna
	 */
	@EventListener
	public void onServiceEventReload(CoreServiceEventReload event) {
		try{
			if(event.getReloadService().name().equals(ReloadServiceConstants.AUDIT.name())){
				logger.info(">>>>>>>>>>>>>>>>>>>>>>> Reload audit service ... IN CORSO <<<<<<<<<<<<<<<<< ");
				
				// stop audit service
				if(currentWriter != null) currentWriter.stopWriter();
	
				/** Determino il writer da usare **/
				currentWriter = buildWriter();
				
				/** Metodo per l'avvio programmatico di audit. **/
				currentWriter.initialize();
				
				logger.info(">>>>>>>>>>>>>>>>>>>>>>> Audit service restarted... TERMINATO <<<<<<<<<<<<<<<<< ");
			}
		}catch(Exception e){
			logger.error("Error reload audit service ... continue anyway " + e, e);
		}
	}
}
